package thread

import (
	"log"
	"strings"
	"time"

	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo/options"
	"notabug.org/ropemann/imabon/src/database"
	"notabug.org/ropemann/imabon/src/entities/post"
	"notabug.org/ropemann/imabon/src/entities/utils"
)

type Thread struct {
	ID         *primitive.ObjectID `json:"ID" bson:"_id,omitempty"`
	AuthorId   int64               `json:"AuthorId" bson:"authorId,omitempty"`
	AuthorName string              `json:"AuthorName" bson:"authorName,omitempty"`
	Title      string              `json:"Title" bson:"title,omitempty"`
	Body       string              `json:"Body" bson:"body,omitempty"`
	Image      string              `json:"Image" bson:"image,omitempty"`
	Posts      []post.Post         `json:"Posts" bson:"posts,omitempty"`
	CreatedAt  *primitive.DateTime `json:"CreatedAt" bson:"createdAt,omitempty"`
	UpdatedAt  *primitive.DateTime `json:"UpdatedAt" bson:"updatedAt,omitempty"`
}

func NewThread(title, authorName, body, image string) *Thread {
	id := primitive.NewObjectID()
	now := primitive.NewDateTimeFromTime(time.Now())

	thread := Thread{
		ID:         &id,
		AuthorId:   utils.GenerateAuthorId(),
		AuthorName: authorName,
		Title:      title,
		Body:       body,
		Image:      image,
		CreatedAt:  &now,
		UpdatedAt:  &now,
	}
	return &thread
}

func (thread Thread) GetMostRecentPost() post.Post {
	if len(thread.Posts) == 0 {
		return post.Post{}
	}
	return thread.Posts[len(thread.Posts)-1]
}

func GetAllThreadsByBoard(board string) []Thread {
	db := database.NewMongoDBConn()
	db.Connect()

	collection := db.Client.Database("threads").Collection(board)
	opts := options.Find().SetSort(bson.D{{Key: "updatedAt", Value: -1}})
	cur, currErr := collection.Find(db.Ctx, bson.D{}, opts)

	if currErr != nil {
		log.Fatal(currErr)
	}
	defer cur.Close(db.Ctx)
	defer db.Close()

	threads := []Thread{}
	if err := cur.All(db.Ctx, &threads); err != nil {
		log.Fatal(err)
	}

	return threads
}

func GetThreadById(board, id string) Thread {
	db := database.NewMongoDBConn()
	db.Connect()

	objId, _ := primitive.ObjectIDFromHex(id)

	collection := db.Client.Database("threads").Collection(board)
	result := collection.FindOne(db.Ctx, bson.M{"_id": objId})
	defer db.Close()

	thread := Thread{}
	err := result.Decode(&thread)
	if err != nil {
		log.Println(err)
		return Thread{}
	}

	return thread

}

func CreateThread(board string, thread Thread) (string, error) {
	db := database.NewMongoDBConn()
	db.Connect()
	defer db.Close()

	collection := db.Client.Database("threads").Collection(board)
	inserted, err := collection.InsertOne(db.Ctx, thread)

	if err != nil {
		return "", err
	}

	b, _ := inserted.InsertedID.(primitive.ObjectID).MarshalJSON()
	insertedString := strings.ReplaceAll(string(b), "\"", "")

	return insertedString, err
}

func GetMostRecentThreads() map[string]Thread {
	db := database.NewMongoDBConn()
	db.Connect()

	collections, err := db.Client.Database("threads").ListCollectionNames(db.Ctx, bson.M{})
	defer db.Close()

	if err != nil {
		log.Println(err)
		return make(map[string]Thread)
	}

	var threadsByBoard = make(map[string]Thread)
	for _, colName := range collections {
		collection := db.Client.Database("threads").Collection(colName)
		opts := options.Find().SetSort(bson.D{{Key: "updatedAt", Value: -1}})
		cur, currErr := collection.Find(db.Ctx, bson.D{}, opts)

		if currErr != nil {
			log.Println(currErr)
			continue
		}
		defer cur.Close(db.Ctx)

		threads := []Thread{}
		if err := cur.All(db.Ctx, &threads); err != nil {
			log.Println(err)
			continue
		}

		if len(threads) > 0 {
			threadsByBoard[colName] = threads[0]
		}
	}

	return threadsByBoard
}

func DeleteThread(board, threadId string) (int64, error) {
	db := database.NewMongoDBConn()
	db.Connect()

	collection := db.Client.Database("threads").Collection(board)
	threadObjId, _ := primitive.ObjectIDFromHex(threadId)
	res, err := collection.DeleteOne(db.Ctx, bson.M{"_id": threadObjId})
	defer db.Close()

	if err != nil {
		return 0, err
	}

	return res.DeletedCount, err
}
